﻿using UnityEngine;
using System.Collections;
using RootMotion.Dynamics;
using RootMotion;

namespace RootMotion.Dynamics
{

    /// <summary>
    /// PuppetMaster prop designed to work with Prop Muscles.
    /// </summary>
    public class PuppetMasterProp : MonoBehaviour
    {
        [Tooltip("Mesh Root will be parented to Prop Muscle's target when this prop is picked up. To make sure the mesh and the colliders match up, Mesh Root's localPosition/Rotation must be zero.")]
        /// <summary>
        /// Mesh Root will be parented to Prop Muscle's target when this prop is picked up. To make sure the mesh and the colliders match up, Mesh Root's localPosition/Rotation must be zero.
        /// </summary>
        public Transform meshRoot;

        [Tooltip("The muscle properties that will be applied to the Prop Muscle when this prop is picked up.")]
        /// <summary>
        /// The muscle properties that will be applied to the Prop Muscle when this prop is picked up.
        /// </summary>
        public Muscle.Props muscleProps;

        [Tooltip("If true, this prop's layer will be forced to PuppetMaster layer and target's layer forced to PuppetMaster's Target Root's layer when the prop is picked up.")]
        /// <summary>
        /// If true, this prop's layer will be forced to PuppetMaster layer and target's layer forced to PuppetMaster's Target Root's layer when the prop is picked up.
        /// </summary>
        public bool forceLayers = true;

        [Tooltip("Mass of the prop while picked up. When dropped, mass of the original Rigidbody will be used.")]
        /// <summary>
        /// Mass of the prop while picked up. When dropped, mass of the original Rigidbody will be used.
        /// </summary>
        public float mass = 1f;

        [Tooltip("This has no other purpose but helping you distinguish props by PropMuscle.currentProp.propType.")]
        /// <summary>
        /// This has no other purpose but helping you distinguish PropMuscle.currentProp by type.
        /// </summary>
        public int propType;

        [LargeHeader("Materials")]

        [Tooltip("If assigned, sets prop colliders to this PhysicMaterial when picked up. If no material assigned, will maintain the original PhysicMaterial (unless otherwise controlled by BehaviourPuppet's Group Overrides).")]
        /// <summary>
        /// If assigned, sets prop colliders to this PhysicMaterial when picked up. If no materials assigned here, will maintain the original PhysicMaterial.
        /// </summary>
        public PhysicsMaterial pickedUpMaterial;

        [LargeHeader("Additional Pin")]

        [Tooltip("Adds this to Prop Muscle's 'Additional Pin Offset' when this prop is picked up.")]
        /// <summary>
        /// Adds this to Prop Muscle's 'Additional Pin Offset' when this prop is picked up.
        /// </summary>
        public Vector3 additionalPinOffsetAdd;

        [Tooltip("The pin weight multiplier of the additional pin. Increasing this weight will make the prop follow animation better, but will increase jitter when colliding with objects.")]
        /// <summary>
        /// The pin weight of the additional pin. Increasing this weight will make the prop follow animation better, but will increase jitter when colliding with objects.
        /// </summary>
        [Range(0f, 1f)] public float additionalPinWeight = 1f;

        [Tooltip("Multiplies the mass of the additional pin by this value when this prop is picked up. The Rigidbody on this prop will be destroyed on pick-up and reattached on drop, so its mass is not used while picked up.")]
        /// <summary>
        /// Multiplies the mass of the additional pin by this value when this prop is picked up. The Rigidbody on this prop will be destroyed on pick-up and reattached on drop, so its mass is not used while picked up.
        /// </summary>
        public float additionalPinMass = 1f;

        /// <summary>
        /// Is the prop currently picked up?
        /// </summary>
        public bool isPickedUp { get; private set; }

        /// <summary>
        /// Returns either the Rigidbody of the prop when it is dropped or the Rigidbody of the prop muscle when it is picked up.
        /// </summary>
        public Rigidbody GetRigidbody()
        {
            if (r != null) return r;
            if (isPickedUp) return propMuscle.rigidbody;
            return null;
        }

        public Vector3 inertiaTensor { get; private set; }
        public Vector3 localCenterOfMass { get; private set; }

        protected virtual void OnPickUp(PuppetMaster puppetMaster, int propMuscleIndex) { }
        protected virtual void OnDrop(PuppetMaster puppetMaster, int propMuscleIndex) { }
        protected Muscle propMuscle { get; private set; }

        private int defaultLayer;
        private Transform defaultParent;
        private Collider[] colliders = new Collider[0];
        private PhysicsMaterial[] droppedMaterials = new PhysicsMaterial[0];

        private Rigidbody r;
        private float _mass;
        private float _drag;
        private float _angularDrag;
        private bool _useGravity;
        private bool _isKinematic;
        private RigidbodyInterpolation _interpolation;
        private CollisionDetectionMode _collisionDetectionMode;
        private RigidbodyConstraints _constraints;
        private Collider[] emptyColliders = new Collider[0];

        // Called by PropMuscle when this prop is picked up
        public void PickUp(PuppetMaster puppetMaster, int propMuscleIndex)
        {
            RemoveRigidbody();

            transform.parent = puppetMaster.muscles[propMuscleIndex].transform;
            transform.position = puppetMaster.muscles[propMuscleIndex].transform.position;
            transform.rotation = puppetMaster.muscles[propMuscleIndex].transform.rotation;

            meshRoot.parent = puppetMaster.muscles[propMuscleIndex].target;
            meshRoot.localPosition = Vector3.zero;
            meshRoot.localRotation = Quaternion.identity;

            puppetMaster.muscles[propMuscleIndex].props = muscleProps;

            if (pickedUpMaterial != null)
            {
                foreach (Collider c in colliders)
                {
                    c.sharedMaterial = pickedUpMaterial;
                }
            }

            if (forceLayers)
            {
                foreach (Collider c in colliders)
                {
                    if (!c.isTrigger) c.gameObject.layer = puppetMaster.muscles[propMuscleIndex].joint.gameObject.layer;
                }
            }

            puppetMaster.muscles[propMuscleIndex].colliders = colliders;
            puppetMaster.UpdateInternalCollisions(puppetMaster.muscles[propMuscleIndex]);

            isPickedUp = true;
            propMuscle = puppetMaster.muscles[propMuscleIndex];

            OnPickUp(puppetMaster, propMuscleIndex);
        }

        // Called by PropMuscle when this prop is dropped
        public void Drop(PuppetMaster puppetMaster, int propMuscleIndex)
        {
            if (!puppetMaster.muscles[propMuscleIndex].joint.gameObject.activeInHierarchy)
            {
                transform.position = puppetMaster.muscles[propMuscleIndex].target.position;
                transform.rotation = puppetMaster.muscles[propMuscleIndex].target.rotation;
            }

            ReattachRigidbody();

            if (!puppetMaster.muscles[propMuscleIndex].joint.gameObject.activeInHierarchy || puppetMaster.muscles[propMuscleIndex].rigidbody.isKinematic)
            {
                if (!r.isKinematic)
                {
                    r.linearVelocity = puppetMaster.muscles[propMuscleIndex].mappedVelocity;
                    r.angularVelocity = puppetMaster.muscles[propMuscleIndex].mappedAngularVelocity;
                }
            }

            transform.parent = defaultParent;

            meshRoot.parent = transform;
            meshRoot.localPosition = Vector3.zero;
            meshRoot.localRotation = Quaternion.identity;

            for (int i = 0; i < colliders.Length; i++)
            {
                colliders[i].sharedMaterial = droppedMaterials[i];
            }

            puppetMaster.ResetInternalCollisions(puppetMaster.muscles[propMuscleIndex], false);
            puppetMaster.muscles[propMuscleIndex].colliders = emptyColliders;

            if (forceLayers)
            {
                foreach (Collider c in colliders)
                {
                    if (!c.isTrigger) c.gameObject.layer = defaultLayer;
                }
            }

            isPickedUp = false;
            propMuscle = null;

            OnDrop(puppetMaster, propMuscleIndex);
        }

        protected virtual void Awake()
        {
            r = GetComponent<Rigidbody>();
            defaultParent = transform.parent;

            colliders = GetComponentsInChildren<Collider>();

            droppedMaterials = new PhysicsMaterial[colliders.Length];
            for (int i = 0; i < colliders.Length; i++)
            {
                droppedMaterials[i] = colliders[i].sharedMaterial;
            }
        }

        protected virtual void Start()
        {
            muscleProps.group = Muscle.Group.Prop;

            if (meshRoot == null)
            {
                Debug.LogError("PuppetMasterProp does not have a 'Mesh Root' Transform assigned.", transform);
                enabled = false;
                return;
            }
            if (meshRoot == transform)
            {
                Debug.LogError("PuppetMasterProp's 'Mesh Root' can not be the PuppetMasterProp's own Transform.", transform);
                enabled = false;
                return;
            }

            defaultLayer = gameObject.layer;
            foreach (Collider c in colliders)
            {
                if (!c.isTrigger)
                {
                    defaultLayer = c.gameObject.layer;
                    break;
                }
            }
        }

        protected virtual void Update()
        {
            if (isPickedUp)
            {
                transform.localPosition = Vector3.zero;
                transform.localRotation = Quaternion.identity;
            }
        }

        private void RemoveRigidbody()
        {
            if (r == null) return;

            inertiaTensor = r.inertiaTensor;
            localCenterOfMass = r.centerOfMass;
            
            _mass = r.mass;
            _drag = r.linearDamping;
            _angularDrag = r.angularDamping;
            _useGravity = r.useGravity;
            _isKinematic = r.isKinematic;
            _interpolation = r.interpolation;
            _collisionDetectionMode = r.collisionDetectionMode;
            _constraints = r.constraints;

            Destroy(r);
        }

        private void ReattachRigidbody()
        {
            if (r != null) return;

            r = gameObject.AddComponent<Rigidbody>();
            r.mass = _mass;
            r.linearDamping = _drag;
            r.angularDamping = _angularDrag;
            r.useGravity = _useGravity;
            r.isKinematic = _isKinematic;
            r.interpolation = _interpolation;
            r.collisionDetectionMode = _collisionDetectionMode;
            r.constraints = _constraints;
        }

        void OnDrawGizmosSelected()
        {
            if (Application.isPlaying) return;

            if (muscleProps != null) muscleProps.group = Muscle.Group.Prop;

            if (meshRoot != null && meshRoot != transform)
            {
                meshRoot.parent = transform;

                meshRoot.position = transform.position;
                meshRoot.rotation = transform.rotation;
            }
        }
    }
}
